Skip to content

【桌游小工具开发记录 01】散樱乱武工具

本篇正式总结“开开桌游小工具”的第一个桌游工具——《散樱乱武》小工具的1.0版本。

项目简介

《散樱乱武》实体桌游面板简述

如果你不熟悉《散樱乱武》这款桌游的基本玩法,建议阅读该部分,能够帮助你了解本工具的作用。

散樱乱武实体桌游面板

散樱乱武实体桌游面板

实体桌游游玩时需要准备这样一块面板,面板上有5种基本区域(“命”、“装”、“气”三个个人区域和「矩」、“虚”两个公共区域),使用特殊角色还可能会追加区域(v1.0版本暂不考虑)。

需要根据游戏说明在各个区域放置「樱花指示物」(即token,上图发光的花瓣图案),放置的token多少表示该区域当前的数值。

游戏过程中会出现大量将token在各个区域之间来回移动的操作,甚至有时会将token移动到卡牌上。但是移动有一定的限制,例如“装”和“矩”区域有数量上限,并且全场的token总数为36,不能凭空消失和产生。

项目内容

由于实际游玩时两块面板尺寸较大,同时token的移动较为繁琐,所以制作该工具用于代替该面板。

v1.0版本整体外观使用圆角矩形+浅阴影,各区域放置主要参考原桌游面板,功能按钮集中放置在后方。游戏以外的额外功能目前只有重置面板(点击左侧重置按钮),以及显示操作帮助(长按左侧重置按钮)。

总体来说,作为第一个正式版本,本工具还非常简陋,但基本功能齐全,满足大部分使用场景,且不存在明显bug。

BC22A8E2-26AF-4D6B-B9A4-8011D56A832B.png

项目重点

  • 如何实现在各区域间移动token;
  • 如何保证移动token时满足数量和区域限制的要求;
  • 如何保证在不同尺寸设备上信息显示正常;
  • 如何实现临时追加区域(付与牌)的添加;
  • 如何实现数据的重置、本地存储和使用;

Store中的state结构

Store中最终存储的state结构为

jsx
// 初始状态数据
    initialState: {
      // 当前回合
      turn: "player2",
			// 公共区域数据
      shared: {
				// 「矩」区域数据
        distance: {
          count: 10,
          limit: 10,
          class: '',
        },
				// 「虚」区域数据
        shadow: {
          count: 0,
          limit: null,
          class: '',
        }
      },
			// 玩家区域数据
      player: {
        // 「装」区域数据
        aura: {
          count: 3,
          limit: 5,
          class: ""
        },
				// ... 「命」、「气」区域数据类似
        // 付与牌
        enhancement: {
          A: {
            count: 0,
            show: false,
            class: "",
          },
					// ...  一共设置了A-G共6个子对象,内容相同
        }
      }
    },
    // 当前回合
    turn: '',
    // 玩家一
    player1: {},
    // 公用区域
    shared: {},
    // 玩家二
    player2: {},
    // 移动token时的相关参数
    movementParas: {
      // 控制token在下次点击时是否应该移动
      from: '',
      to: '',
      isReadyToMove: false,
      amount: 1,
    },

如何实现在各区域间移动token

这实际上可以直接抽象为一个简单问题:总数36如何在8个区域之间自由调动,且不能有 出现负数、超过区域上限、凭空消失和增加这些问题。

移动的方式有两种 :

  1. “前进”、“后退”等这些固定动作的移动,token的来源和目标区域均固定;
  2. 任意两个区域间的自由移动;
  3. 移动到付与牌上,来源区域固定,目标区域为最新打出的付与牌;

同一组件不同实例间的数据互通

这里毫无疑问就是使用Vuex进行跨组件间的数据共享,但这里一开始让我有点犯难,因为我在这个页面其实使用的是两种组件,一个是中间包括“距”和“虚”区域的组件,另一个是代表玩家个人区域的组件,为了追求简洁,玩家个人区域的组件(SA_player.vue)中只有一个玩家的相关内容,另一名玩家则是同样同一个组件然后进行翻转后放置在页面中的,等于是说用一个组件生成两个实例,并且需要这两个实例能够进行数据互通。

我使用的方案是在使用组件时分别传入两个不同的参数“player1“,”player2“,然后组件中则设置根据参数的不同读取store中不同的数据。

使用lodash简化对state的访问和修改

使用lodash这个包中的getupdate方法,可以直接使用字符串作为参数获取对象中的数据和更新数据,特别是两个不同的player实例需要根据props参数选择性地访问state中的数据,这时,直接使用字符串拼接,然后去访问state内容是相对简洁的方式。

例如:

jsx
// 获取token来源区域的当前数量,
const fromAreaTokenCount = _.get(state, state.movementParas.from)

// 更新token来源区域的数量
_.update(state, state.movementParas.from, (n) => n - state.movementParas.amount)

其中的state.movementParas.fromstate.movementParas.amount均为组件实例中拼接后传入的字符串数据。

如果不使用lodash,通常需要分别设置from1、from2两个参数来获取来源区域的token数量,例如:

Untitled

这样写的代码会非常冗长,不利于后期维护。

移动相关参数存储在Store中

从上面可以看到,移动相关的参数movementParas是存储在Store中的,这也避免了外部组件调用移动的mutation时还需要传入参数。即

jsx
move(arg1, arg2) // 传入参数进行移动
// ↓↓拆分为两步↓↓
modifyMoveParas(arg1, arg2) // 修改移动相关参数
move() //移动

同时,这样也保证了未来可以适应不同的需求,例如点击「前进」、「后退」等按钮,其移动的参数是固定的,这样写可能就会有点冗长,但是当两个组件的区域间进行token的移动时,通常移动的目标区域的组件并不知道token来源于其他组件的哪个区域,这样将移动的参数存储在store中也解决了这样的问题。

如何保证移动token时满足数量和区域限制的要求

这个问题只要在移动token前获取「来源区域」token数量、「目标区域」token数量、「目标区域」token上限、移动的token数量,然后进行计算比对即可。当出现数量不足、超过上限等问题时则使用uni.showToast()方法进行弹窗提示。

不过这种弹窗方式有点不好,其调用的是小程序的弹窗组件,显示的文字方向始终正对其中一名玩家,而另对侧玩家无法正常查看提示,所以后续需要自己重写一个弹窗组件。

如何保证在不同尺寸设备上信息显示正常

首先当前的工具外观以文字为主并不是一个好方法,后续需要使用图形等其他手段更高效地传达信息。

对于当前版本,为了使各个区块在不同设备上均显示完全,其宽高均使用vhvw进行设定,布局使用弹性布局。player组件在底部添加以vh为单位的padding,以保证底部操控条不会遮挡内容,margin则使用px,只要保证各区块间不显得拥挤即可。

最重要的文字内容则要保证其最小尺寸,并且会跟随屏幕变大而等比例变大,目前暂时使用的是font-size: calc(30px + 6vw)的方案,后续需要测试并修改为max()方案,以便更加直观。

如何实现临时追加区域(付与牌)的添加

Untitled

除了原有的几个区域,当在游戏中打出付与牌时,token也会从「虚」和「装」区域移动到牌上。这时就需要追加和上方区域类似的付与牌区域。我的处理方法是预先在store中定义A-G总共7个付与牌的对象数据,然后在player.vue中遍历数据生成7个区域,但是使用v-if根据其token数量和show参数来判断是否显示。同样的,区域点击事件和需要的自定义属性也在这时添加上去,所以这些新生成的区域能和其他区域一样点击移动token。

不过点击「打出付与牌」按钮后需要进行二次确认,这时不应能进行其他操作,所以在区域点击函数和token移动函数上均添加了针对付与牌的判断,用于防止误操作。但是当前判断的依据是几个区域的class,后续考虑为面板添加总状态state参数,根据该参数来控制面板中可执行动作,以及各区域的样式,这样能使代码更符合逻辑。

同样的,在实际测试中发现7个付与牌预留区域过多,后续考虑动态添加/删除区域,减少未利用空间,多出的空间可以给付与牌添加基本功能,以修改该功能的逻辑,更符合实际游玩时的逻辑。

如何实现数据的重置、本地存储和使用

在store的state中有一个单独的初始化用的数据(initialState),用来对其他数据进行初始化。在页面文件sakura_arms.vue中,在onLoad()时调用相关函数读取本地数据,如本地数据为空则使用初始化数据进行重置,而重置按钮则直接调用重置函数进行重置。

存储和读取本地数据直接使用uni.setStorageSync()uni.getStorageSync()函数,将数据以JSON的形式进行存储和读取。